home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 5 / MacMania 5.toast / / Internet software / NewsWatcher / NW Source / Source / url.c < prev    next >
Text File  |  1997-01-09  |  35KB  |  1,373 lines

  1. /*----------------------------------------------------------------------------
  2.  
  3.     url.c
  4.  
  5.     This module handles parsing and opening URLs.
  6.     
  7.     Copyright © 1994-1997, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <ctype.h>
  14. #include <stdlib.h>
  15.  
  16. #include "glob.h"
  17. #include "url.h"
  18. #include "article.h"
  19. #include "strutil.h"
  20. #include "dialog.h"
  21. #include "message.h"
  22. #include "apputil.h"
  23. #include "fileutil.h"
  24. #include "newswatcher.h"
  25. #include "memutil.h"
  26. #include "resutil.h"
  27. #include "sfutil.h"
  28. #include "wind.h"
  29. #include "full.h"
  30. #include "subject.h"
  31. #include "ic.h"
  32.  
  33.  
  34.  
  35. #define isurlschemechar(c)                (isalnum((c)) || c == '+' || c == '.' || c == '-')
  36.     
  37. #define kOptionClickDlg                    162
  38. #define kCantFindHelperDlgID            153
  39.  
  40. #define kFetchCreatorType                'FTCh'
  41. #define kFetchMinVersionNumber            0x02120000
  42. #define kFetchMinVersionNumberStr        "2.1.2"
  43. #define kFetchMinVersionNumberGURL        0x03000000
  44.  
  45. #define kAnarchieCreatorType            'Arch'
  46. #define kAnarchieMinVersionNumber        0x01200000
  47. #define kAnarchieMinVersionNumberStr    "1.2.0"
  48. #define kAnarchieMinVersionNumberGURL    0x01402003
  49.  
  50. #define kFTPHelperURLFileType            'AURL'
  51.  
  52. #define kNetscapeCreatorType            'MOSS'
  53. #define kNetscapeMinVersionNumber        0x00096003
  54. #define kNetscapeMinVersionNumberStr    "0.93 beta"
  55.  
  56. #define kMacWebCreatorType                'MWEB'
  57. #define kMacWebMinVersionNumber            0x00000000
  58. #define kMacWebMinVersionNumberStr        ""
  59. #define kMacWebMinVersionNumberGURL        0x01004003
  60.  
  61. #define kMacWAISCreatorType                'MWAS'
  62. #define kMacWAISMinVersionNumber        0x01298000
  63. #define kMacWAISMinVersionNumberStr        "1.2.9"
  64.  
  65. #define kMacWebAndWAISOpenURLEventClass    'wwwc'
  66. #define kMacWebAndWAISOpenURLEventID    'ourl'
  67. #define kMacWebAndWAISOpenURLKeyword    'kURL'
  68.  
  69. #define kNCSAMosaicCreatorType            'MOS!'
  70. #define kNCSAMosaicMinVersionNumber        0x02004008
  71. #define kNCSAMosaicMinVersionNumberStr    "2.0a8"
  72. #define kNCSAMosaicOpenURLEventClass    'mos!'
  73. #define kNCSAMosaicOpenURLEventID        'ourl'
  74.  
  75. #define kTurboGopherCreatorType            'TGOF'
  76. #define kTurboGopherMinVersionNumber    0x02004001
  77. #define kTurboGopherMinVersionNumberStr    "2.0a1"
  78. #define kTurboGopherMinVersionNumberCanon    0x02006001
  79.  
  80. #define kNCSATelnetCreatorType            'NCSA'
  81. #define kNCSATelnetSettingsFileType        'CONF'
  82. #define kNCSATelnetMinVersionNumber        0x02400000
  83. #define kNCSATelnetMinVersionNumberStr    "2.4"
  84. #define kNCSATelnetMinVersionNumberGURL    0x02612004
  85.  
  86. #define ktn3270CreatorType                'GFTM'
  87. #define ktn3270SettingsFileType            'GFTS'
  88. #define ktn3270MinVersionNumber            0x02402007
  89. #define ktn3270MinVersionNumberStr        "2.4d7"
  90.  
  91. #define kFingerCreatorType                'PnLF'
  92. #define kFingerMinVersionNumber            0x01500000
  93. #define kFingerMinVersionNumberStr        "1.5.0"
  94.  
  95. #define kPhCreatorType                    'PHED'
  96. #define kPhMinVersionNumber                0x01200000
  97. #define kPhMinVersionNumberStr            "1.2"
  98.  
  99.  
  100.  
  101. typedef enum TURLKind {
  102.     kNotURL,
  103.     kMailtoURL,
  104.     kNewsURL,
  105.     kNntpURL,
  106.     kOtherURL
  107. } TURLKind;
  108.  
  109. typedef struct THelperInfo {
  110.     OSType sig;                                /* signature of helper program */
  111.     unsigned long minVersionNumber;            /* min version number of helper program */
  112.     char *minVersionStr;                    /* min version number as a string */
  113. } THelperInfo;
  114.  
  115. static THelperInfo gHelperInfo[] = {
  116.     {kAnarchieCreatorType, kAnarchieMinVersionNumber,
  117.         kAnarchieMinVersionNumberStr},
  118.     {kFetchCreatorType, kFetchMinVersionNumber,
  119.         kFetchMinVersionNumberStr},
  120.     {kNetscapeCreatorType, kNetscapeMinVersionNumber,
  121.         kNetscapeMinVersionNumberStr},
  122.     {kMacWebCreatorType, kMacWebMinVersionNumber,
  123.         kMacWebMinVersionNumberStr},
  124.     {kMacWAISCreatorType, kMacWAISMinVersionNumber,
  125.         kMacWAISMinVersionNumberStr},
  126.     {kNCSAMosaicCreatorType, kNCSAMosaicMinVersionNumber,
  127.         kNCSAMosaicMinVersionNumberStr},
  128.     {kTurboGopherCreatorType, kTurboGopherMinVersionNumber,
  129.         kTurboGopherMinVersionNumberStr},
  130.     {kNCSATelnetCreatorType, kNCSATelnetMinVersionNumber,
  131.         kNCSATelnetMinVersionNumberStr},
  132.     {ktn3270CreatorType, ktn3270MinVersionNumber,
  133.         ktn3270MinVersionNumberStr},
  134.     {kFingerCreatorType, kFingerMinVersionNumber,
  135.         kFingerMinVersionNumberStr},
  136.     {kPhCreatorType, kPhMinVersionNumber,
  137.         kPhMinVersionNumberStr},
  138.     {0, 0, nil}
  139. };
  140.  
  141. typedef struct TDefaultHelperInfo {
  142.     TURLSchemeName schemeName;
  143.     OSType helpers[5];
  144. } TDefaultHelperInfo;
  145.  
  146. static TDefaultHelperInfo gDefaultHelperInfo[] = {
  147.     {"ftp", {kFetchCreatorType, kAnarchieCreatorType, 0, 0, 0}},
  148.     {"http", {kMacWebCreatorType, kNetscapeCreatorType, kNCSAMosaicCreatorType, 0, 0}},
  149.     {"gopher", {kTurboGopherCreatorType, kNetscapeCreatorType, kMacWebCreatorType, 
  150.         kNCSAMosaicCreatorType, 0}},
  151.     {"wais", {kMacWAISCreatorType, kNCSAMosaicCreatorType, 0, 0, 0}},
  152.     {"telnet", {kNCSATelnetCreatorType, 0, 0, 0, 0}},
  153.     {"tn3270", {ktn3270CreatorType, 0, 0, 0, 0}},
  154.     {"finger", {kFingerCreatorType, 0, 0, 0, 0}},
  155.     {"whois", {kFingerCreatorType, 0, 0, 0, 0}},
  156.     {"ph", {kPhCreatorType, 0, 0, 0, 0}},
  157.     {"", {0, 0, 0, 0, 0}},
  158. };    
  159.  
  160.  
  161.  
  162. /*----------------------------------------------------------------------------
  163.     CheckOneDefaultURLHelper 
  164.     
  165.     Check to see if we have a usable version of a default URL helper program.
  166.     
  167.     Entry:    sig = signature of helper program
  168.     
  169.     Exit:    function result = true if we have a usable version of this helper.
  170.             *versionNumber = version number of helper.
  171.             *lastMod = last mod date/time of helper
  172. ----------------------------------------------------------------------------*/
  173.  
  174. static Boolean CheckOneDefaultURLHelper (OSType sig, unsigned long *versionNumber,
  175.     unsigned long *lastMod)    
  176. {
  177.     THelperInfo *p;
  178.     OSErr err = noErr;
  179.     FSSpec fSpec;
  180.         
  181.     /* Find the program on the user's disks. */
  182.     
  183.     err = FindAppFromSig(sig, &fSpec, nil, nil);
  184.     if (err != noErr) return false;
  185.  
  186.     /* Find the entry for this program in the gHelperInfo array. */
  187.     
  188.     for (p = gHelperInfo; ; p++) {
  189.         if (p->sig == 0) return false;
  190.         if (p->sig == sig) break;
  191.     }
  192.     
  193.     /* Check the version number. */
  194.     
  195.     if (p->minVersionNumber != 0) {
  196.         err = GetAppVersionNumber(&fSpec, versionNumber);
  197.         if (err != noErr) return false;
  198.         if (*versionNumber < p->minVersionNumber) return false;
  199.     }
  200.     
  201.     /* Get the last mod date and time. */
  202.     
  203.     err = GetLastModDateTime(&fSpec, lastMod);
  204.     if (err != noErr) return false;
  205.     
  206.     return true;
  207. }
  208.  
  209.  
  210.  
  211. /*----------------------------------------------------------------------------
  212.     InitDefaultURLHelper 
  213.     
  214.     Initialize default URL helper info.
  215.     
  216.     Entry:    schemeName = pointer to scheme name.
  217. ----------------------------------------------------------------------------*/
  218.  
  219. void InitDefaultURLHelper (TURLSchemeName schemeName)
  220. {
  221.     short i;
  222.     TURLHelperInfo *p;
  223.     TDefaultHelperInfo *q;
  224.     OSType *helpers, sig;
  225.     unsigned long versionNumber, lastMod;
  226.     
  227.     /* Find the entry for this scheme in the gPrefs.urlHelpers array. If we can't
  228.        find the entry, or if a helper is already set for this scheme, return. */
  229.     
  230.     for (i = 0, p = *gPrefs.urlHelpers; ; i++, p++) {
  231.         if (*p->schemeName == 0) return;
  232.         if (strcmp(schemeName, p->schemeName) == 0) break;
  233.     }
  234.     if (p->sig != 0) return;
  235.     
  236.     /* Search the gDefaultHelperInfo array to find the list of helper sigs
  237.        for this scheme. */
  238.     
  239.     for (q = gDefaultHelperInfo; ; q++) {
  240.         if (*q->schemeName == 0) return;
  241.         if (strcmp(schemeName, q->schemeName) == 0) break;
  242.     }
  243.     helpers = q->helpers;
  244.     
  245.     /* For each sig in the list of helper sigs for this scheme, check to see if
  246.        the user has a copy of the program which is usable as a helper. If we find
  247.        one, set it in the gPrefs.urlHelpers array entry for this scheme. */
  248.  
  249.     while (*helpers != 0) {
  250.         sig = *helpers;
  251.         helpers++;
  252.         if (CheckOneDefaultURLHelper(sig, &versionNumber, &lastMod)) {
  253.             p = &(*gPrefs.urlHelpers)[i];
  254.             p->sig = sig;
  255.             p->versionNumber = versionNumber;
  256.             p->lastMod = lastMod;
  257.             return;
  258.         }
  259.     }
  260. }
  261.  
  262.  
  263.  
  264. /*----------------------------------------------------------------------------
  265.     ValidURLHelper 
  266.     
  267.     Check a file to see if it is a valid URL helper program.
  268.     
  269.     Entry:    fSpec = pointer to application file spec.
  270.             helperInfo = pointer to helper info.
  271.             
  272.     Exit:    function result = true if valid.
  273.             if the helper is valid:
  274.                 helperInfo->sig = signature of helper.
  275.                 helperInfo->versionNumber = version number of helper.
  276.                 helperInfo->lastMod = last mod date/time of helper.
  277.             
  278.     This function is called when the user selects a new URL helper in the
  279.     preferences dialog. It checks minimum version numbers for the known
  280.     URL helper programs. If the helper is valid, the helper info is filled
  281.     in. If the helper is not valid, an error message is issued.
  282.     
  283.     This function is also recalled whenever the user runs a URL helper
  284.     and the last mod date/time of the helper as recorded in the helper info
  285.     does not match the last mod date/time of the file being executed.
  286. ----------------------------------------------------------------------------*/
  287.  
  288. Boolean ValidURLHelper (FSSpec *fSpec, TURLHelperInfo *helperInfo)
  289. {
  290.     OSErr err = noErr;
  291.     FInfo fInfo;
  292.     OSType sig, sig2;
  293.     unsigned long versionNumber = 0;
  294.     unsigned long minVersionNumber = 0;
  295.     unsigned long lastMod;
  296.     short j;
  297.     CStr255 fmt, msg;
  298.     
  299.     err = FSpGetFInfo(fSpec, &fInfo);
  300.     if (err != noErr) goto exit;
  301.     sig = fInfo.fdCreator;
  302.     
  303.     for (j = 0; ; j++) {
  304.         sig2 = gHelperInfo[j].sig;
  305.         if (sig2 == 0 || sig == sig2) break;
  306.     }
  307.     if (sig2 != 0) minVersionNumber = gHelperInfo[j].minVersionNumber;
  308.  
  309.     if (minVersionNumber != 0) {
  310.         err = GetAppVersionNumber(fSpec, &versionNumber);
  311.         if (err == resNotFound) return false;
  312.         if (err != noErr) goto exit;
  313.         if (versionNumber < minVersionNumber) {
  314.             GetCString(kStrHelperTooOld, fmt);
  315.             p2cstr(fSpec->name);
  316.             sprintf(msg, fmt, fSpec->name, gHelperInfo[j].minVersionStr);
  317.             c2pstr((char*)fSpec->name);
  318.             StopAlertMessage(msg);
  319.             return false;
  320.         }
  321.     }
  322.     
  323.     err = GetLastModDateTime(fSpec, &lastMod);
  324.     if (err != noErr) goto exit;
  325.  
  326.     helperInfo->sig = sig;
  327.     helperInfo->versionNumber = versionNumber;
  328.     helperInfo->lastMod = lastMod;
  329.     
  330.     return true;
  331.     
  332. exit:
  333.  
  334.     GetCString(kStrUnexpectedErr, fmt);
  335.     sprintf(msg, fmt, err);
  336.     StopAlertMessage(msg);
  337.     return false;
  338. }
  339.  
  340.  
  341.  
  342. /*----------------------------------------------------------------------------
  343.     DoCantFindHelperDialog 
  344.     
  345.     Present a "can't find helper program" dialog.
  346.     
  347.     Entry:    msg = error message.
  348. ----------------------------------------------------------------------------*/
  349.  
  350. void DoCantFindHelperDialog (char *msg)
  351. {
  352.     DialogPtr dlg;
  353.     short item;
  354.     OSErr err = noErr;
  355.  
  356.     c2pstr(msg);
  357.     ParamText((StringPtr)msg, "\p", "\p", "\p");
  358.     p2cstr((StringPtr)msg);
  359.     err = MyGetNewDialog(kCantFindHelperDlgID, ok, 0, &dlg);
  360.     SysBeep(0);
  361.     if (err != noErr) return;
  362.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  363.     DoClose(dlg);
  364. }
  365.  
  366.  
  367.  
  368. /*----------------------------------------------------------------------------
  369.     HelperErrorMessage
  370.     
  371.     Issue a helper program error message.
  372.     
  373.     Entry:    err = error number.
  374.             sig = helper program signature.
  375.     
  376.     Exit:    function result = userCanceledErr.
  377. ----------------------------------------------------------------------------*/
  378.  
  379. static OSErr HelperErrorMessage (OSErr err, OSType sig)
  380. {
  381.     CStr255 fmt, msg;
  382.     Str31 name;
  383.  
  384.     *name = 0;
  385.     FindAppNameFromSig(sig, name);
  386.     p2cstr(name);
  387.     if (err == fnfErr) {
  388.         if (*name == 0) {
  389.             GetCString(kStrCantFindURLHelper, msg);
  390.         } else {
  391.             GetCString(kStrURLHelperNotFound, fmt);
  392.             sprintf(msg, fmt, name);
  393.         }
  394.         DoCantFindHelperDialog(msg);
  395.     } else if (err == memFullErr || err == memFragErr || err == appMemFullErr) {
  396.         GetCString(kStrURLHelperNoMem, fmt);
  397.         sprintf(msg, fmt, name);
  398.         ErrorMessage(msg);
  399.     } else {
  400.         GetCString(kStrURLHelperUnexpectedErr, fmt);
  401.         sprintf(msg, fmt, err, name);
  402.         ErrorMessage(msg);
  403.     }
  404.     return userCanceledErr;
  405. }
  406.  
  407.  
  408.  
  409. /*----------------------------------------------------------------------------
  410.     IsSlackMailtoURL
  411.     
  412.     Check for a slack emailto URL.
  413.     
  414.     Entry:    text = handle to text.
  415.             begin = offset in text of beginning of object.
  416.             end = offset in text of end of object.
  417.             
  418.     Exit:    function result = true if slack emailto URL.
  419.             url = parsed URL.
  420. ----------------------------------------------------------------------------*/
  421.  
  422. static Boolean IsSlackMailtoURL (Handle text, short begin, short end, CStr255 url)
  423. {
  424.     char *p, *q, *x, *y, *textEnd;
  425.     long len;
  426.     Boolean couldBeEmailAddress = false;
  427.     
  428.     p = *text + begin;
  429.     q = *text + end;
  430.     textEnd = *text + MyGetHandleSize(text);
  431.     
  432.     if (*p == '<') {
  433.         x = p - 1;
  434.         while (x > *text && *x != CR) x--;
  435.         if (x > *text) x++;
  436.         if (MyStrNEqual(x, "From", 4)) {
  437.             y = x+4;
  438.         } else if (MyStrNEqual(x, "Reply-To", 8)) {
  439.             y = x+8;
  440.         } else {
  441.             y = nil;
  442.         }
  443.         if (y != nil) {
  444.             while (isLWSP(*y) && y < p) y++;
  445.             if (y < p && *y == ':') couldBeEmailAddress = true;
  446.         } else {
  447.             y = q + 1;
  448.             while (y < textEnd && isLWSP(*y)) y++;
  449.             if (y + 6 <= textEnd && MyStrNEqual(y, "wrote:", 6))
  450.                 couldBeEmailAddress = true;
  451.         }
  452.         if (!couldBeEmailAddress) return false;
  453.     }
  454.     
  455.     x = p;
  456.     while (*x != '@' && x < q) x++;
  457.     if (x >= q) return false;
  458.     while (*x != '.' && x < q) x++;
  459.     if (x >= q) return false;
  460.     
  461.     if (*p == '<') {
  462.         p++;
  463.         q--;
  464.     }
  465.     len = q - p + 1;
  466.     if (len == 0 || len > 255) return false;
  467.     BlockMoveData(p, url, len);
  468.     url[len] = 0;
  469.     return true;
  470. }
  471.  
  472.  
  473.  
  474. /*----------------------------------------------------------------------------
  475.     IsSlackNewsURL
  476.     
  477.     Check for a slack news URL.
  478.     
  479.     Entry:    text = handle to text.
  480.             begin = offset in text of beginning of object.
  481.             end = offset in text of end of object.
  482.             
  483.     Exit:    function result = true if slack news URL.
  484.             url = parsed URL.
  485. ----------------------------------------------------------------------------*/
  486.  
  487. static Boolean IsSlackNewsURL (Handle text, short begin, short end, CStr255 url)
  488. {
  489.     char *p, *q, *x;
  490.     long len;
  491.     
  492.     p = *text + begin;
  493.     q = *text + end;
  494.     
  495.     x = p;
  496.     if (*x != '<') return false;
  497.     while (*x != '@' && x < q) x++;
  498.     if (x >= q) return false;
  499.     while (*x != '.' && x < q) x++;
  500.     if (x >= q) return false;
  501.  
  502.     len = q - p + 1;
  503.     if (len == 0 || len > 255) return false;
  504.     BlockMoveData(p, url, len);
  505.     url[len] = 0;
  506.     return true;
  507. }
  508.  
  509.  
  510.  
  511. /*----------------------------------------------------------------------------
  512.     ParseURL
  513.     
  514.     Parse a URL.
  515.     
  516.     Entry:    text = handle to text.
  517.             begin = offset in text of beginning of object.
  518.             end = offset in text of end of object.
  519.             
  520.     Exit:    function result = kind of URL.
  521.             url = parsed URL.
  522.             *urlBegin = offset in text of beginning of parsed URL.
  523.             *urlEnd = offset in text of end of parsed URL.
  524.             *index = index in gPrefs.urlHelpers array of helper info
  525.                 for parsed URL scheme, if function result = kOtherURL.
  526.             
  527.     If begin == end, ParseURL tries to locate the beginning and end of 
  528.     the URL.
  529. ----------------------------------------------------------------------------*/
  530.  
  531. static TURLKind ParseURL (Handle text, short begin, short end, CStr255 url,
  532.     short *urlBegin, short *urlEnd, short *index)
  533. {
  534.     char *textEnd, *p, *q, *x, *y, *a, *b;
  535.     long len;
  536.     TURLKind urlKind;
  537.     Boolean isMultiLineURL = false, mightBeMultiLineURL = false;
  538.     short numCR;
  539.     TURLHelperInfo *helperInfo;
  540.     short i;
  541.     TURLSchemeName schemeName;
  542.     
  543.     p = *text + begin;
  544.     q = *text + end - 1;
  545.     
  546.     if (begin == end) {
  547.     
  548.         textEnd = *text + MyGetHandleSize(text);
  549.         
  550.         numCR = 0;
  551.         x = p;
  552.         y = q;
  553.         while (true) {
  554.             while (x >= *text && *x != '<' && *x != '>' && 
  555.                 *x != '"' && *x != CR) x--;
  556.             if (x < *text || *x == '>' || *x == '"') break;
  557.             if (*x == CR) {
  558.                 numCR++;
  559.                 if (numCR > 10) break;
  560.                 x--;
  561.             } else { /* *x == '<' */
  562.                 a = x+1;
  563.                 while (a < textEnd && isurlschemechar(*a)) a++;
  564.                 mightBeMultiLineURL = a < textEnd && *a == ':';
  565.                 break;
  566.             }
  567.         }
  568.         if (mightBeMultiLineURL) {
  569.             numCR = 0;
  570.             while (true) {
  571.                 while (y < textEnd && *y != '<' && *y != '>' && 
  572.                     *y != '"' && *y != CR) y++;
  573.                 if (y >= textEnd || *y == '<' || *y == '"') break;
  574.                 if (*y == CR) {
  575.                     numCR++;
  576.                     if (numCR > 10) break;
  577.                     y++;
  578.                 } else { /* *y == '>' */
  579.                     isMultiLineURL = true;
  580.                     break;
  581.                 }
  582.             }
  583.         }
  584.         
  585.         if (isMultiLineURL) {
  586.             p = x;
  587.             q = y;
  588.         } else {
  589.             while (p >= *text && !isLWSPorCR(*p) && *p != '<' && *p != '"') p--;
  590.             if (*p != '<') p++;
  591.             while (q < textEnd && !isLWSPorCR(*q) && *q != '>' && *q != '"') q++;
  592.             if (*q != '>') q--;
  593.         }
  594.         
  595.         if (p >= q) return kNotURL;
  596.     
  597.     }
  598.     
  599.     while (p < q && isLWSPorCR(*p)) p++;
  600.     while (p < q && isLWSPorCR(*q)) q--;
  601.     
  602.     if (*p == '<') {
  603.         if (*q != '>') return kNotURL;
  604.     } else {
  605.         if (*q == '>') return kNotURL;
  606.     } 
  607.     
  608.     *urlBegin = p - *text;
  609.     *urlEnd = q - *text + 1;
  610.     x = p;
  611.     if (*x == '<') x++;
  612.     y = x;
  613.     while (isurlschemechar(*y) && y < q) y++;
  614.     if (*y == ':') {
  615.         if (MyStrNEqual(x, "url:", 4)) x += 4;
  616.         if (*q == '>') q--;
  617.         len = q - x + 1;
  618.         if (len == 0 || len > 255) return kNotURL;
  619.         BlockMoveData(x, url, len);
  620.         url[len] = 0;
  621.         b = url;
  622.         while (*b != ':' && *b != 0) b++;
  623.         if (*b == ':') {
  624.             for (a = url; a < b; a++) *a = tolower(*a);
  625.         }
  626.         if (MyStrNEqual(url, "mailto", 6)) {
  627.             urlKind = kMailtoURL;
  628.             goto exit;
  629.         }
  630.         if (MyStrNEqual(url, "news", 4)) {
  631.             urlKind = kNewsURL;
  632.             goto exit;
  633.         }
  634.         if (MyStrNEqual(url, "nntp", 4)) {
  635.             urlKind = kNntpURL;
  636.             goto exit;
  637.         }
  638.         for (a = url, b = schemeName; 
  639.             *a != 0 && *a != ':' && b < schemeName + kMaxSchemeNameLen;
  640.             a++, b++) *b = *a;
  641.         *b = 0;
  642.         MyICReadURLHelperPref(schemeName);
  643.         for (i = 0, helperInfo = *gPrefs.urlHelpers;
  644.             *helperInfo->schemeName != 0; 
  645.             i++, helperInfo++) 
  646.         {
  647.             if (MyStrEqual(schemeName, helperInfo->schemeName)) {
  648.                 urlKind = kOtherURL;
  649.                 *index = i;
  650.                 goto exit;
  651.             }
  652.         }
  653.     }
  654.     
  655.     begin = p - *text;
  656.     end = q - *text;
  657.     if (IsSlackMailtoURL(text, begin, end, url)) {
  658.         urlKind = kMailtoURL;
  659.         goto exit;
  660.     }
  661.     if (IsSlackNewsURL(text, begin, end, url)) {
  662.         urlKind = kNewsURL;
  663.         goto exit;
  664.     }
  665.     return kNotURL;
  666.     
  667. exit:
  668.  
  669.     /* Strip line breaks and white space around line breaks from the url. */
  670.  
  671.     for (p = q = url; *p != 0; p++) {
  672.         if (*p == CR) {
  673.             q--;
  674.             while (q >= url && isLWSP(*q)) q--;
  675.             q++;
  676.             p++;
  677.             while (*p != 0 && isLWSP(*p)) p++;
  678.             p--;
  679.         } else {
  680.             *q++ = *p;
  681.         }
  682.     }
  683.     *q = 0;
  684.     return urlKind;
  685. }
  686.  
  687.  
  688.  
  689. /*----------------------------------------------------------------------------
  690.     ParseNntpURL
  691.     
  692.     Parse an nntp URL.
  693.     
  694.     Entry:    url = URL string.
  695.     
  696.     Exit:    function result = error code (paramErr if syntax error).
  697.             host = news server host address.
  698.             *port = port number.
  699.             newsgroup = newsgroup name.
  700.             *artNumber = article number.
  701. ----------------------------------------------------------------------------*/
  702.  
  703. static OSErr ParseNntpURL (char *url, char *host, short *port, 
  704.     char *newsgroup, long *artNumber)
  705. {
  706.     char *p, *q;
  707.     
  708.     p = url + strlen("nntp://");
  709.     
  710.     q = host;
  711.     while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
  712.     *q = 0;
  713.     
  714.     if (*p == ':') {
  715.         *port = atoi(p+1);
  716.         while (*p != '/' && *p != 0) p++;
  717.     } else {
  718.         *port = kNNTPPort;
  719.     }
  720.     
  721.     if (*p != '/') return paramErr;
  722.     p++;
  723.     q = newsgroup;
  724.     while (*p != '/' && *p != 0) *q++ = *p++;
  725.     *q = 0;
  726.     
  727.     if (*p != '/') return paramErr;
  728.     *artNumber = atol(p+1);
  729.  
  730.     return noErr;
  731. }
  732.  
  733.  
  734.  
  735. /*----------------------------------------------------------------------------
  736.     CreateAURLFile
  737.     
  738.     Create Fetch or Anarchie AURL file.
  739.     
  740.     Entry:    sig = signature of helper.
  741.             url = URL string.
  742.     
  743.     Exit:    function result = error code.
  744.             *docSpec = file spec of created AURL file.
  745. ----------------------------------------------------------------------------*/
  746.  
  747. static OSErr CreateAURLFile (OSType sig, char *url, FSSpec *docSpec)
  748. {
  749.     OSErr err = noErr;
  750.     short fRefNum = 0, len;
  751.     char cmd[1000];
  752.     long count;
  753.     
  754.     err = CreateTemporaryFile(docSpec, kNewsWatcherSignature, sig, kFTPHelperURLFileType);
  755.     if (err != noErr) return err;
  756.     err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
  757.     if (err != noErr) return err;
  758.     len = strlen(url);
  759.     sprintf(cmd, "FCH %s", url);
  760.     count = strlen(cmd);
  761.     err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
  762.     MyFSClose(fRefNum, nil);
  763.     return err;
  764. }
  765.  
  766.  
  767.  
  768. /*----------------------------------------------------------------------------
  769.     CreateNCSATelnetSettingsFile
  770.     
  771.     Create an NCSA Telnet settings file.
  772.     
  773.     Entry:    url = URL string.
  774.     
  775.     Exit:    function result = error code.
  776.             *docSpec = file spec of created settings file.
  777. ----------------------------------------------------------------------------*/
  778.  
  779. static OSErr CreateNCSATelnetSettingsFile (char *url, FSSpec *docSpec)
  780. {
  781.     OSErr err = noErr;
  782.     short fRefNum = 0;
  783.     char cmd[1000];
  784.     long count;
  785.     CStr255 hostName;
  786.     short port;
  787.     char *p, *q;
  788.     
  789.     err = CreateTemporaryFile(docSpec, kNewsWatcherSignature, 
  790.         kNCSATelnetCreatorType, kNCSATelnetSettingsFileType);
  791.     if (err != noErr) return err;
  792.     err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
  793.     if (err != noErr) return err;
  794.     
  795.     p = url + strlen("telnet://");
  796.     q = p;
  797.     while (*q != '@' && *q != 0) q++;
  798.     if (*q == '@') p = q+1;
  799.     q = hostName;
  800.     while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
  801.     *q = 0;
  802.     if (*p == ':') {
  803.         port = atoi(p+1);
  804.     } else {
  805.         port = 23;
  806.     }
  807.     sprintf(cmd, "name=\"%s\"\rhost=\"%s\"\rport=\"%d\"\r", url, hostName, port);
  808.  
  809.     count = strlen(cmd);
  810.     err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
  811.     MyFSClose(fRefNum, nil);
  812.     return err;
  813. }
  814.  
  815.  
  816.  
  817. /*----------------------------------------------------------------------------
  818.     CreateTn3270SettingsFile
  819.     
  820.     Create a tn3270 settings file.
  821.     
  822.     Entry:    url = URL string.
  823.     
  824.     Exit:    function result = error code.
  825.             *docSpec = file spec of created settings file.
  826. ----------------------------------------------------------------------------*/
  827.  
  828. static OSErr CreateTn3270SettingsFile (char *url, FSSpec *docSpec)
  829. {
  830.     OSErr err = noErr;
  831.     short fRefNum = 0;
  832.     char cmd[1000];
  833.     long count;
  834.     CStr255 hostName;
  835.     char *p, *q;
  836.     
  837.     err = CreateTemporaryFile(docSpec, kNewsWatcherSignature, 
  838.         ktn3270CreatorType, ktn3270SettingsFileType);
  839.     if (err != noErr) return err;
  840.     err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
  841.     if (err != noErr) return err;
  842.     
  843.     p = url + strlen("tn3270://");
  844.     q = p;
  845.     while (*q != '@' && *q != 0) q++;
  846.     if (*q == '@') p = q+1;
  847.     q = hostName;
  848.     while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
  849.     *q = 0;
  850.     sprintf(cmd, "host_name=\"%s\"\rwindow_title=\"%s\"\r", hostName, url);
  851.  
  852.     count = strlen(cmd);
  853.     err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
  854.     MyFSClose(fRefNum, nil);
  855.     return err;
  856. }
  857.  
  858.  
  859.  
  860. /*----------------------------------------------------------------------------
  861.     OpenHelperWithURL
  862.     
  863.     Open a helper program and pass it a URL string.
  864.     
  865.     Entry:    helperInfo = pointer to helper info.
  866.             url = URL string.
  867.             foreground = true to run helper in foreground, false to run it
  868.                 in the background.
  869.     
  870.     Exit:    function result = error code.
  871. ----------------------------------------------------------------------------*/
  872.  
  873. static OSErr OpenHelperWithURL (TURLHelperInfo *helperInfo, CStr255 url,
  874.     Boolean foreground)
  875. {
  876.     OSErr err = noErr;
  877.     ProcessSerialNumber psn;
  878.     FSSpec appSpec, docSpec;
  879.     Boolean running;
  880.     unsigned short launchControlFlags;
  881.     char hackedURL[500];
  882.     unsigned long lastMod;
  883.     
  884.     launchControlFlags = launchContinue | launchNoFileFlags | launchUseMinimum;
  885.     if (!foreground) launchControlFlags |= launchDontSwitch;
  886.         
  887.     err = FindAppFromSig(helperInfo->sig, &appSpec, &running, &psn);
  888.     if (err != noErr) goto exit;
  889.     
  890.     err = GetLastModDateTime(&appSpec, &lastMod);
  891.     if (err != noErr) goto exit;
  892.     if (lastMod != helperInfo->lastMod) {
  893.         if (!ValidURLHelper(&appSpec, helperInfo)) return userCanceledErr;
  894.     }
  895.     
  896.     switch (helperInfo->sig) {
  897.     
  898.         case kFetchCreatorType:
  899.         
  900.             if (helperInfo->versionNumber >= kFetchMinVersionNumberGURL) {
  901.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  902.                     kGetURLEventClass, kGetURLEventID,
  903.                     keyDirectObject, url, 0, launchControlFlags);
  904.             } else {
  905.                 err = CreateAURLFile(helperInfo->sig, url, &docSpec);
  906.                 if (err != noErr) goto exit;
  907.                 err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  908.                     launchControlFlags);
  909.             }
  910.             break;
  911.  
  912.         case kAnarchieCreatorType:
  913.  
  914.             if (helperInfo->versionNumber >= kAnarchieMinVersionNumberGURL) {
  915.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  916.                     kGetURLEventClass, kGetURLEventID,
  917.                     keyDirectObject, url, 0, launchControlFlags);
  918.             } else {
  919.                 err = CreateAURLFile(helperInfo->sig, url, &docSpec);
  920.                 if (err != noErr) goto exit;
  921.                 err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  922.                     launchControlFlags);
  923.             }
  924.             break;
  925.             
  926.         case kMacWebCreatorType:
  927.         
  928.             if (helperInfo->versionNumber >= kMacWebMinVersionNumberGURL) {
  929.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  930.                     kGetURLEventClass, kGetURLEventID,
  931.                     keyDirectObject, url, 0, launchControlFlags);
  932.             } else {
  933.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  934.                     kMacWebAndWAISOpenURLEventClass, kMacWebAndWAISOpenURLEventID,
  935.                     kMacWebAndWAISOpenURLKeyword, url, 0, launchControlFlags);
  936.             }
  937.             break;
  938.             
  939.         case kMacWAISCreatorType:
  940.         
  941.             err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  942.                 kMacWebAndWAISOpenURLEventClass, kMacWebAndWAISOpenURLEventID,
  943.                 kMacWebAndWAISOpenURLKeyword, url, 0, launchControlFlags);
  944.             break;
  945.             
  946.         case kNCSAMosaicCreatorType:
  947.         
  948.             err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  949.                 kNCSAMosaicOpenURLEventClass, kNCSAMosaicOpenURLEventID,
  950.                 keyDirectObject, url, 0, launchControlFlags);
  951.             break;
  952.             
  953.         case kTurboGopherCreatorType:
  954.  
  955.             if (helperInfo->versionNumber < kTurboGopherMinVersionNumberCanon) {
  956.                 /* Special case for version 2.0a1 - must add <URL:...> wrapper. */
  957.                 if (MyStrNEqual(url, "<URL:", 5)) {
  958.                     strcpy(hackedURL, url);
  959.                 } else if (MyStrNEqual(url, "URL:", 4)) {
  960.                     sprintf(hackedURL, "<%s>", url);
  961.                 } else {
  962.                     sprintf(hackedURL, "<URL:%s>", url);
  963.                 }
  964.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  965.                     kGetURLEventClass, kGetURLEventID,
  966.                     keyDirectObject, hackedURL, 0, launchControlFlags);
  967.             } else {
  968.                 /* Versions >= 2.0b1 accept canoncial form without wrapper. */
  969.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  970.                     kGetURLEventClass, kGetURLEventID,
  971.                     keyDirectObject, url, 0, launchControlFlags);
  972.             }
  973.             break;
  974.             
  975.         case kNCSATelnetCreatorType:
  976.         
  977.             if (helperInfo->versionNumber >= kNCSATelnetMinVersionNumberGURL) {
  978.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  979.                     kGetURLEventClass, kGetURLEventID,
  980.                     keyDirectObject, url, 0, launchControlFlags);
  981.             } else {
  982.                 err = CreateNCSATelnetSettingsFile(url, &docSpec);
  983.                 if (err != noErr) goto exit;
  984.                 err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  985.                     launchControlFlags);
  986.             }
  987.             break;
  988.             
  989.         case ktn3270CreatorType:
  990.         
  991.             err = CreateTn3270SettingsFile(url, &docSpec);
  992.             if (err != noErr) goto exit;
  993.             err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  994.                 launchControlFlags);
  995.             break;
  996.             
  997.         default:
  998.         
  999.             err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  1000.                 kGetURLEventClass, kGetURLEventID,
  1001.                 keyDirectObject, url, 0, launchControlFlags);
  1002.             break;
  1003.             
  1004.     }
  1005.     
  1006.     if (err != noErr) goto exit;
  1007.     
  1008.     return noErr;
  1009.     
  1010. exit:
  1011.  
  1012.     return HelperErrorMessage(err, helperInfo->sig);
  1013. }
  1014.  
  1015.  
  1016.  
  1017. /*----------------------------------------------------------------------------
  1018.     FlashObject
  1019.     
  1020.     Flash an object in an article, message, or text window.
  1021.     
  1022.     Entry:    theTE = handle to TextEdit record.
  1023.             start = starting position of object.
  1024.             end = ending position of object.
  1025. ----------------------------------------------------------------------------*/
  1026.  
  1027. static void FlashObject (TEHandle theTE, short start, short end)
  1028. {
  1029.     short i;
  1030.     long finalTick;
  1031.     
  1032.     TESetSelect(start, end, theTE);
  1033.     for (i = 0; i < 2; i++) {
  1034.         Delay(5, &finalTick);
  1035.         TEDeactivate(theTE);
  1036.         Delay(5, &finalTick);
  1037.         TEActivate(theTE);
  1038.     }
  1039. }
  1040.  
  1041.  
  1042.  
  1043. /*----------------------------------------------------------------------------
  1044.     ProcessURLEscapeCodes
  1045.     
  1046.     Process "%xx" escape codes in a URL string (replace them by the characters
  1047.     they represent).
  1048.     
  1049.     Entry:    url = URL with escape codes.
  1050.             
  1051.     Exit:    url = URL with escape codes replaced by the characters they
  1052.                 represent.
  1053. ----------------------------------------------------------------------------*/
  1054.  
  1055. static void ProcessURLEscapeCodes (char *url)
  1056. {
  1057.     char *p, *q;
  1058.     char c1, c2;
  1059.     
  1060.     p = q = url;
  1061.     while (*p != 0) {
  1062.         if (*p == '%') {
  1063.             c1 = tolower(*(p+1));
  1064.             c2 = tolower(*(p+2));
  1065.             if (isxdigit(c1) && isxdigit(c2)) {
  1066.                 c1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
  1067.                 c2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
  1068.                 *q++ = (c1 << 4) + c2;
  1069.                 p += 3;
  1070.             } else {
  1071.                 *q++ = *p++;
  1072.             }
  1073.         } else {
  1074.             *q++ = *p++;
  1075.         }
  1076.     }
  1077.     *q = 0;
  1078. }
  1079.  
  1080.  
  1081.  
  1082. /*----------------------------------------------------------------------------
  1083.     OpenNewsURL
  1084.     
  1085.     Open a news URL or slack URL.
  1086.     
  1087.     Entry:    url = news URL or slack URL.
  1088.             
  1089.     Exit:    function result = error code (fnfErr if article or group not found).
  1090. ----------------------------------------------------------------------------*/
  1091.  
  1092. static OSErr OpenNewsURL (char *url)
  1093. {
  1094.     char *p;
  1095.     long item;
  1096.     Boolean hasArts;
  1097.  
  1098.     if (MyStrNEqual(url, "news:", 5)) {
  1099.         if (url[5] == '*' && url[6] == 0) {
  1100.             /* Show full group list window */
  1101.             if (((WindowPeek)gFullGroupWindow)->visible) {
  1102.                 MySelectWindow(gFullGroupWindow);
  1103.                 return noErr;
  1104.             } else {
  1105.                 return DoShowHideFullGroupList();
  1106.             }
  1107.         } else {
  1108.             for (p = url; *p != 0; p++)
  1109.                 if (*p == '@')
  1110.                     return OpenReferencedArticle(url);
  1111.             /* Open group from full group list */
  1112.             item = FindGroupIndex(url+5);
  1113.             if (item == -1) return fnfErr;
  1114.             return OpenGroupItem(gFullGroupWindow, item, gPrefs.maxFetch, &hasArts);
  1115.         }
  1116.     } else {
  1117.         return OpenReferencedArticle(url);
  1118.     }
  1119.     return noErr;
  1120. }
  1121.  
  1122.  
  1123.  
  1124. /*----------------------------------------------------------------------------
  1125.     OpenURL
  1126.     
  1127.     Open a URL.
  1128.     
  1129.     Entry:    wind = pointer to article, message, or text window.
  1130.             theTE = handle to text edit record.
  1131.             begin = offset in text of beginning of url.
  1132.             end = offset in text of end of url.
  1133.             
  1134.     Exit:    function result = error code.
  1135. ----------------------------------------------------------------------------*/
  1136.  
  1137. static OSErr OpenURL (WindowPtr wind, TEHandle theTE, short begin, short end)
  1138. {
  1139.     TWindow **info;
  1140.     CStr255 url, msg;
  1141.     short urlBegin, urlEnd;
  1142.     TURLKind urlKind;
  1143.     TURLHelperInfo helperInfo;
  1144.     Boolean foreground, isFTP;
  1145.     CStr255 host, newsgroup;
  1146.     short port, index, len;
  1147.     long artNumber;
  1148.     OSErr err = noErr;
  1149.     TURLSchemeName schemeName;
  1150.     short i;
  1151.     TURLHelperInfo *p;
  1152.     
  1153.     info = (TWindow**)GetWRefCon(wind);
  1154.     urlKind = ParseURL((**theTE).hText, begin, end, url, &urlBegin, &urlEnd, &index);
  1155.     if (urlKind == kNotURL) {
  1156.         SysBeep(0);
  1157.         return userCanceledErr;
  1158.     }
  1159.     FlashObject(theTE, urlBegin, urlEnd);
  1160.     
  1161.     switch (urlKind) {
  1162.     
  1163.         case kMailtoURL:
  1164.         
  1165.             ProcessURLEscapeCodes(url);
  1166.             return OpenMailWindow(url);
  1167.             
  1168.         case kNewsURL:
  1169.         
  1170.             ProcessURLEscapeCodes(url);
  1171.             err = OpenNewsURL(url);
  1172.             if (err == fnfErr) {
  1173.                 NoteMessageNumber(kStrArticleOrGroupNotFound);
  1174.                 return userCanceledErr;
  1175.             } else {
  1176.                 return err;
  1177.             }
  1178.             
  1179.         case kNntpURL:
  1180.         
  1181.             ProcessURLEscapeCodes(url);
  1182.             err = ParseNntpURL(url, host, &port, newsgroup, &artNumber);
  1183.             if (err == paramErr) {
  1184.                 ErrorMessageNumber(kStrNntpURLSyntaxError);
  1185.                 return userCanceledErr;
  1186.             } else if (err != noErr) {
  1187.                 return err;
  1188.             }
  1189.             err = OpenArticleOnAnyServer(host, port, newsgroup, artNumber);
  1190.             if (err == fnfErr) {
  1191.                 NoteMessageNumber(kStrArticleOrGroupNotFound);
  1192.                 return userCanceledErr;
  1193.             } else {
  1194.                 return err;
  1195.             }
  1196.             
  1197.         case kOtherURL:
  1198.         
  1199.             strcpy(schemeName, (*gPrefs.urlHelpers)[index].schemeName);
  1200.             isFTP = MyStrEqual(schemeName, "ftp");
  1201.             if (isFTP && gPrefs.useWebHelperForHtmlFiles) {
  1202.                 len = strlen(url);
  1203.                 if ((len > 5 && MyStrNEqual(url + len - 5, ".html", 5)) ||
  1204.                     (len > 4 && MyStrNEqual(url + len - 4, ".htm", 4))) 
  1205.                 {
  1206.                     for (i = 0, p = *gPrefs.urlHelpers; *p->schemeName != 0; i++, p++) 
  1207.                     {
  1208.                         if (MyStrEqual("http", p->schemeName)) {
  1209.                             index = i;
  1210.                             strcpy(schemeName, "http");
  1211.                             isFTP = false;
  1212.                             break;
  1213.                         }
  1214.                     }
  1215.                 }
  1216.             }
  1217.             InitDefaultURLHelper(schemeName);
  1218.             helperInfo = (*gPrefs.urlHelpers)[index];
  1219.             foreground = !isFTP || url[strlen(url)-1] == '/';
  1220.             if (helperInfo.sig != 0) {
  1221.                 err = OpenHelperWithURL(&helperInfo, url, foreground);
  1222.                 (*gPrefs.urlHelpers)[index] = helperInfo;
  1223.                 return err;
  1224.             } else {
  1225.                 GetCString(kStrCantFindDefaultURLHelper, msg);
  1226.                 DoCantFindHelperDialog(msg);
  1227.                 return userCanceledErr;
  1228.             }
  1229.                 
  1230.     }
  1231.     
  1232.     return err;
  1233. }
  1234.  
  1235.  
  1236.  
  1237. /*----------------------------------------------------------------------------
  1238.     CommandClick
  1239.     
  1240.     Handle a command-click in an article, message, or text window.
  1241.     
  1242.     Entry:    wind = pointer to article, message, or text window.
  1243.             theTE = handle to TextEdit record in which command-click occurred.
  1244.             oldSelStart = start of selection range before user command-clicked.
  1245.             oldSelEnd = end of selection range before user command-clicked.
  1246.             modifiers = modifiers field from event record.
  1247.             
  1248.     Exit:    function result = error code.
  1249. ----------------------------------------------------------------------------*/
  1250.  
  1251. OSErr CommandClick (WindowPtr wind, TEHandle theTE, 
  1252.     short oldSelStart, short oldSelEnd, short modifiers)
  1253. {
  1254.     TWindow **info;
  1255.     short selStart, selEnd, begin, end;
  1256.     DialogPtr dlg;
  1257.     short item;
  1258.     OSErr err = noErr;
  1259.     
  1260.     if (!gStartupOK) return noErr;
  1261.     
  1262.     if ((modifiers & optionKey) == 0 && (modifiers & cmdKey) == 0) return noErr;
  1263.     
  1264.     if ((modifiers & cmdKey) == 0) {
  1265.         err = MyGetNewDialog(kOptionClickDlg, ok, cancel, &dlg);
  1266.         SysBeep(0);
  1267.         if (err != noErr) return err;
  1268.         MyModalDialog(dlg, gDialogFilterUPP, &item);
  1269.         DoClose(dlg);
  1270.         if (item == cancel) return userCanceledErr;
  1271.     }
  1272.         
  1273.     info = (TWindow**)GetWRefCon(wind);
  1274.     selStart = (**theTE).selStart;
  1275.     selEnd = (**theTE).selEnd;
  1276.     if (selStart != selEnd) return noErr;
  1277.     
  1278.     if (oldSelStart < oldSelEnd && oldSelStart <= selStart && 
  1279.         selStart <= oldSelEnd) 
  1280.     {
  1281.         begin = oldSelStart;
  1282.         end = oldSelEnd;
  1283.     } else {
  1284.         begin = end = selStart;
  1285.     }
  1286.     
  1287.     return OpenURL(wind, theTE, begin, end);
  1288. }
  1289.  
  1290.  
  1291.  
  1292. /*----------------------------------------------------------------------------
  1293.     DoOpenURL
  1294.     
  1295.     Handle the "Open URL" command.
  1296.     
  1297.     Entry:    wind = pointer to article, message, or text window.
  1298.             
  1299.     Exit:    function result = error code.
  1300. ----------------------------------------------------------------------------*/
  1301.  
  1302. OSErr DoOpenURL (WindowPtr wind)
  1303. {
  1304.     TWindow **info;
  1305.     TEHandle theTE;
  1306.     TMsgFieldInfo **fields;
  1307.     short curField;
  1308.     
  1309.     info = (TWindow**)GetWRefCon(wind);
  1310.     if ((**info).kind == kMessage) {
  1311.         fields = (**info).fields;
  1312.         curField = (**info).curField;
  1313.         theTE = (*fields)[curField].edit;
  1314.     } else {
  1315.         theTE = (**info).theTE;
  1316.     }
  1317.     return OpenURL(wind, theTE, (**theTE).selStart, (**theTE).selEnd);
  1318. }
  1319.  
  1320.  
  1321.  
  1322. /*----------------------------------------------------------------------------
  1323.     OpenURLString
  1324.     
  1325.     Open a news, nntp, or mailto URL.
  1326.     
  1327.     Entry:    urlString = the URL string.
  1328.             
  1329.     Exit:    function result = error code.
  1330. ----------------------------------------------------------------------------*/
  1331.  
  1332. OSErr OpenURLString (char *urlString)
  1333. {
  1334.     OSErr err = noErr;
  1335.     Handle h = nil;
  1336.     short len, urlBegin, urlEnd;
  1337.     CStr255 url;
  1338.     TURLKind urlKind;
  1339.     CStr255 host, newsgroup;
  1340.     short port;
  1341.     long artNumber;
  1342.     short index;
  1343.  
  1344.     len = strlen(urlString);
  1345.     err = MyPtrToHand(urlString, &h, len);
  1346.     if (err != noErr) return err;
  1347.     urlKind = ParseURL(h, 0, len, url, &urlBegin, &urlEnd, &index);
  1348.     MyDisposeHandle(h);
  1349.     ProcessURLEscapeCodes(url);
  1350.     
  1351.     switch (urlKind) {
  1352.     
  1353.         case kMailtoURL:
  1354.         
  1355.             return OpenMailWindow(url);
  1356.             
  1357.         case kNewsURL:
  1358.         
  1359.             return OpenNewsURL(url);
  1360.             
  1361.         case kNntpURL:
  1362.         
  1363.             err = ParseNntpURL(url, host, &port, newsgroup, &artNumber);
  1364.             if (err != noErr) return err;
  1365.             return OpenArticleOnAnyServer(host, port, newsgroup, artNumber);
  1366.             
  1367.         default:
  1368.         
  1369.             return paramErr;
  1370.             
  1371.     }
  1372. }
  1373.